import time
import os
from typing import List, Tuple, Any

from fuzzer.GreyBoxFuzzer import GreyBoxFuzzer
from schedule.PathPowerSchedule import PathPowerSchedule, get_path_id
from runner.FunctionCoverageRunner import FunctionCoverageRunner
from runner.Runner import Runner
from utils.ObjectUtils import dump_object, load_object, get_md5_of_object


class PathGreyBoxFuzzer(GreyBoxFuzzer):
    """Count how often individual paths are exercised."""

    def __init__(self, seeds: List[str], schedule: PathPowerSchedule, is_print: bool):
        super().__init__(seeds, schedule, False)

        # TODO
        self.paths = {}
        self.last_path_time = self.start_time
        self.total_paths = 0
        self.seed_dir = os.path.join(os.getcwd(), "_result", "seeds")

        os.makedirs(self.seed_dir, exist_ok=True)

        print("""
┌───────────────────────┬───────────────────────┬───────────────────────┬───────────────────┬───────────────────┬────────────────┬───────────────────┐
│        Run Time       │     Last New Path     │    Last Uniq Crash    │    Total Execs    │    Total Paths    │  Uniq Crashes  │   Covered Lines   │
├───────────────────────┼───────────────────────┼───────────────────────┼───────────────────┼───────────────────┼────────────────┼───────────────────┤""")

    def print_stats(self):
        def format_seconds(seconds):
            hours = int(seconds) // 3600
            minutes = int(seconds % 3600) // 60
            remaining_seconds = int(seconds) % 60
            return f"{hours:02d}:{minutes:02d}:{remaining_seconds:02d}"

        template = """│{runtime}│{path_time}│{crash_time}│{total_exec}│{total_path}│{uniq_crash}│{covered_line}│
├───────────────────────┼───────────────────────┼───────────────────────┼───────────────────┼───────────────────┼────────────────┼───────────────────┤"""
        template = template.format(runtime=format_seconds(time.time() - self.start_time).center(23),
                                   path_time=format_seconds(self.last_path_time - self.start_time).center(23),
                                   crash_time=format_seconds(self.last_crash_time - self.start_time).center(23),
                                   total_exec=str(self.total_execs).center(19),
                                   total_path=str(self.total_paths).center(19),
                                   uniq_crash=str(len(set(self.crash_map.values()))).center(16),
                                   covered_line=str(len(self.covered_line)).center(19))
        print(template)

    def run(self, runner: FunctionCoverageRunner) -> Tuple[Any, str]:  # type: ignore
        """Inform scheduler about path frequency"""
        result, outcome = super().run(runner)

        # TODO
        if outcome == Runner.PASS and self.inp in self.crash_map:
            del self.crash_map[self.inp]
        path_id = get_path_id(runner.coverage())

        if path_id not in self.paths:
            self.last_path_time = time.time()
            self.paths[path_id] = True
            self.total_paths = len(self.paths)

        self.schedule.update_path_frequencies(self.population)

        return result, outcome

    def add_seed_to_population(self, seed):
        seed_hash = get_md5_of_object(seed.data)
        file_path = os.path.join(self.seed_dir, f"{seed_hash}.seed")
        dump_object(file_path, seed)
        self.file_map[seed_hash] = file_path
        self.population.append(seed_hash)
